package com.telenav.comm;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;

import com.telenav.comm.ICommCallback.CommResponse;
import com.telenav.network.HttpConnection;
import com.telenav.network.NetworkConnection;
import com.telenav.network.NetworkManager;
import com.telenav.network.SocketConnection;
import com.telenav.threadpool.IJob;

class RequestJob implements IJob
{
    String requestId;
    Host host;
    int retryTimes;
    long timout;
    ICommCallback commCallBack;
    Comm comm;
    
    CommResponse response;
    long pendingTime;
    long executeTime;
    boolean isCancelled = false;
    boolean isRunning = false;
    int retry = 0;
    Hashtable requestHeaders = null;
    
    NetworkConnection connection;
    OutputStream os;
    InputStream is;
    
    boolean isSocketAlive = false;
    
    private Object waitObj = new Object();
    private boolean dataHandled;
    
    public RequestJob(String requestId, Host host, byte[] request, int retryTimes, long timeout,
            ICommCallback commCallBack, Comm comm, Hashtable requestHeaders)
    {
        pendingTime = System.currentTimeMillis();
        
        this.requestId = requestId;
        this.host = host;
        this.retryTimes = retryTimes;
        this.timout = timeout;
        this.commCallBack = commCallBack;
        this.comm = comm;
        this.requestHeaders = requestHeaders;
        
        
        if(Host.SOCKET.equals(host.protocol))//if socket, will use long socket, so add 2 more retry times.
        {
            this.retryTimes += 2;
        }
        
        response = new CommResponse(this.requestId, ICommCallback.NO_DATA);
        
        response.jobId = this.requestId;
        response.requestData = request;
    }

    public void cancel()
    {
        if (isCancelled)
        {
            return;
        }

        response.isCanceled = true;
        isCancelled = true;
    }

    public void execute(int handlerID)
    {
        isRunning = true;
        dataHandled = false;      
        
        try
        {           
            if (isCancelled)
                return;
            
            if(this.commCallBack != null && !this.commCallBack.isAllowNetworkRequest(response))
            {
                cancel();
                this.commCallBack.networkError(response);
                return;
            }
            
            try
            {
                connection = this.comm.networkManager.openConnection(this.comm.getHostProvider().getUrl(host), NetworkManager.READ_WRITE, true);
            }
            catch (Exception e)
            {      
                response.errorMessage = e.getMessage();
                this.response.exception = e;
                
                return;
            }
            
            if(connection == null)
            {
                return;
            }
            
            if(connection instanceof HttpConnection)
            {
                HttpConnection httpConnection = (HttpConnection) connection;

                boolean isSuccessful = HttpStreamHandler.handle(httpConnection, this);
                if (!isSuccessful)
                {
                    return;
                }
                else
                {
                    response.status = ICommCallback.SUCCESS;
                }
            }
            else if(connection instanceof SocketConnection)
            {
                SocketConnection socketConnection = (SocketConnection) connection;
                
                boolean isSuccessful = SocketStreamHandler.handle(socketConnection, this);
                if (!isSuccessful)
                {
                    return;
                }
                else
                {
                    response.status = ICommCallback.SUCCESS;
                }
            }
            
        }
        finally
        {

            if (!isCancelled)
            {
                if (response.status != ICommCallback.SUCCESS)
                {
                    if (retry < retryTimes)
                    {
                        retry++;

                        try
                        {
                            Thread.sleep(Comm.REQUEST_JOB_RETRY_WAIT_TIME * (1 << (retry - 1)));
                        }
                        catch (Exception ex)
                        {
                        }

                        this.comm.addJob(this, true);
                        
                        this.close();
                        return;
                    }
                }
            }
            
            if (!isCancelled)
            {
                if (response.status == ICommCallback.SUCCESS)
                {
                    this.commCallBack.networkFinished(response);
                    dataHandled = true;
                }
                else
                {
                    this.commCallBack.networkError(response);
                }
            }
            
            close();
            
            isRunning = false;
            
            synchronized (waitObj)
            {
                waitObj.notify();
            }

        }
    }

    public boolean isCancelled()
    {
        return isCancelled;
    }

    public boolean isRunning()
    {
        return isRunning;
    }

    void timeout()
    {
        if(this.isCancelled())
        {
            return;
        }
        
        response.status = ICommCallback.TIMEOUT;
        
        cancel();
        
        if(this.commCallBack != null)
        {
            try
            {
                this.commCallBack.networkError(response);
            }
            catch(Exception e)
            {
            }
        }
    }
    
    void close()
    {
        if(isSocketAlive && Host.SOCKET.equals(host.protocol))
        {
            return;
        }
        
        try
        {
            if (os != null)
            {
                os.close();
            }
        }
        catch (Exception e)
        {
        }
        finally
        {
            os = null;
        }

        try
        {
            if (is != null)
            {
                is.close();
            }
        }
        catch (Exception e)
        {
        }
        finally
        {
            is = null;
        }
        
        try
        {
            if(connection != null)
            {
                connection.close();
            }
        }
        catch (Exception e)
        {
        }
        finally
        {
            connection = null;
        }
    }

    public void networkFallbacked()
    {
        if (this.isCancelled() || !isRunning)
        {
            return;
        }
        
        this.cancel();
        
        try
        {
            if (isRunning)
            {
                synchronized (waitObj)
                {
                    waitObj.wait(15000);
                }
            }
        }
        catch (Exception e)
        {
        }
        
        if (dataHandled)
        {
            return;
        }
        
        this.isCancelled = false;
        
        retry = 0;
        
        this.comm.addJob(this, true);
        
        this.close();
    }
}
